package mytest.sfa;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Random;

import sde.actor.Actor;
import sde.actor.ControlMachine;
import sde.actor.Message;
import sde.actor.Path;
import sde.actor.Simulation;
import sde.actor.distribution.Distribution;
import sde.actor.distribution.ExponentialDistribution;


class Job {
	private static int IDS = 0;
	private final int id;

	public Job() {
		id = IDS++;
	}

	public int getId() {
		return id;
	}
}

class Statistics {
	
	private ArrayList<Double> timers = new ArrayList<>();
	private int n = 0;
	
	void arrival(Job j, double time){
		timers.add(j.getId(), time);
	}
	
	void departure(Job j, double time){
		double tmp = timers.get(j.getId());
		timers.add(j.getId(), time-tmp);
		n++;
	}
	
	double responseTime(){
		double sum = 0;
		for(Double d:timers){
			sum+=d.doubleValue();
		}
		return sum/n;
	}
	
	double throughput(Double tend){
		return n/tend;
	}
	
}

class Source extends Actor{
	
	private static class Init extends Message {
		Station station;
		Distribution d;
		Statistics s;
		public Init(Station station, Distribution d, Statistics s) {
			super();
			this.s=s;
			this.station = station;
			this.d = d;
		}
	}
	
	private static class Generate extends Message{}
	
	private final byte CREATED=0, GENERATING=1;
	private Station station;
	private Distribution d;
	private Statistics s;
	
	public Source(){
		become(CREATED);
	}
	
	public void init(Station st, Distribution d, Statistics s){
		send(new Init(st, d, s));
	}

	@Override
	public void handler(Message m) {
		switch (currentStatus()) {
		case CREATED:
			if (m instanceof Init) {
				Init i = (Init) m;
				d = i.d;
				station = i.station;
				s=i.s;
				send(new Generate(), now() + d.nextSample());
				become(GENERATING);
			}
			break;
		case GENERATING:
			if(m instanceof Generate){
				Job j = new Job();
				station.send(new Station.Arrival(j));
				s.arrival(j, now());
				send(m, now()+d.nextSample());
			}
			break;
		}
	}	
}

class Station extends Actor{
	
	private static class Init extends Message{
		Path p;
		Statistics s;
		Distribution d;

		private Init(Path p, Statistics s, Distribution d) {
			super();
			this.d = d;
			this.p = p;
			this.s = s;
		}
	}
	
	static class Arrival extends Message{
		Job j;
		
		public Arrival(Job j){
			this.j=j;
		}
	}
	
	static class Departure extends Message{
		Job j;
		
		public Departure(Job j){
			this.j=j;
		}
	}
	
	private Distribution d;
	private Path p;
	private Statistics s;
	private LinkedList<Message> defers = new LinkedList<Message>();
	private final byte CREATED=0, FREE=1, BUSY=2;
	
	public Station() {
		become(CREATED);
	}
	
	public void init(Path p, Statistics s,Distribution d){
		send(new Init(p, s, d));
	}

	@Override
	public void handler(Message m) {
		switch (currentStatus()) {
		case CREATED:
			if (m instanceof Init) {
				Init i = (Init) m;
				d = i.d;
				s = i.s;
				p = i.p;
				become(FREE);
			}
			break;
		case FREE:
			if (m instanceof Arrival) {
				Arrival a = (Arrival) m;
				send(new Departure(a.j), now() + d.nextSample());
				p.up(now());
				become(BUSY);
			}
			break;
		case BUSY:
			if (m instanceof Departure) {
				Departure d = (Departure) m;
				p.down(now());
				double time = now() + this.d.nextSample();
				s.departure(d.j, time);
				if (defers.size() == 0)
					become(FREE);
				else {
					defers.removeFirst();
					send(new Departure(d.j), time);
				}
			} else if (m instanceof Arrival) {
				p.up(now());
				defers.addLast(m);
			}
			break;
		}
	}
}

public class SFA {

	public static void main(String[] args) throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		double mediaInterarrivi = 0.0, mediaServizi = 0.0;
		double tEnd = 0;
		System.out
				.println("Fornisci su linee separate mediaInterarrivi(1/lambda di source)"
						+ " mediaServizi(1/mu di station) tEnd");
		mediaInterarrivi = Double.valueOf(br.readLine()).doubleValue();
		System.out.println("1/lambda=" + mediaInterarrivi);
		mediaServizi = Double.valueOf(br.readLine()).doubleValue();
		System.out.println("1/mu=" + mediaServizi);
		tEnd = Double.valueOf(br.readLine()).doubleValue();
		System.out.println("tEnd=" + tEnd);
		ControlMachine cm = new Simulation(tEnd);
		Path p = new Path();
		Statistics s =  new Statistics();
		Station station = new Station();
		station.init(p, s, new ExponentialDistribution(1/mediaServizi, new Random()));
		Source source = new Source();
		source.init(station, new ExponentialDistribution(1/mediaInterarrivi, new Random()),s);
		cm.controller();
		System.out.println("Numero medio di clienti in attesa = "
				+ String.format("%1.2f%n", p.mean(tEnd)));
		System.out.println("Tempo di risposta = "
				+ String.format("%1.2f%n", s.responseTime()));
		System.out.println("Throughput = "
				+ String.format("%1.2f%n", s.throughput(tEnd)));
	}

}
